#include "Renderer.h"
//#include "Vector.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>

#if defined(_OS_WINDOWS_)
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;
PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL;
#endif

static SDL_Surface *screen;

static char *messages[]=
{
	"  \240\241\242\243\244\245\246 Playable Demo     \211\212",
	" Distributed by: M. Andrzejak ",
	#if defined(_OS_WINDOWS_)
	"       Windows port by:       ",
	#elif defined(_OS_LINUX_)
	"        Linux port by:        ",
	#else
	"      Mac OS X port by:       ",
	#endif
	"      WAHa.06x36 & coda       ",
	//" [mandrzej@et.put.poznan.pl]  ",
	"Go through the gates 1-2-3-4-1",
	"      as fast as you can !    ",
	"My best time is 10:16 !       ",
	//"Full game will be             ",
	//"available soon.               ",
	"                              ",
	"\224\225\221time \226\227\221 go to this gate   ",
	"Keyboard control:\221\220 turn      ",
	"\222\223 forward & backward         ",
	"[SPACE] stoop and jump        ",
	"[SHIFT] speedup               ",
	//"[F1][F2][F3] - screen size    ",
	//"[S] 3D Stereo \213\214\215\216            ",
	//"\213\214\215\216 left:green, right:red    ",
	//"[O] distance \230\231 \230\231 in \213\214\215\216 3D ",
	//"[B] brightness balance in 3D  ",
	//"[D] or wait 30 sec.  -  DEMO  ",
	"[Del] back to start           ",
	"[Esc] exit                    ",
	"           Music:             ",
	"  *** Stardust memories ***   ",
	"  composed by jester/sanity   ",
	"                              ",
};

int InitRenderer(Renderer *renderer,char *palfile)
{
	if(SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO|SDL_INIT_JOYSTICK|SDL_INIT_NOPARACHUTE)<0)
	{
		fprintf(stderr,"Unable to init SDL: %s\n",SDL_GetError());
		return 0;
	}

	atexit(SDL_Quit);

//	SDL_GL_SetAttribute(SDL_GL_RED_SIZE,5);
//	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,5);
//	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,5);
//	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,32);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);

	const SDL_VideoInfo *info;
	info=SDL_GetVideoInfo();
	screen=SDL_SetVideoMode(800,600,info->vfmt->BitsPerPixel,SDL_OPENGL); //|SDL_FULLSCREEN);
	
	if(!screen)
	{
		fprintf(stderr,"Unable to set video mode: %s\n",SDL_GetError());
		return 0;
	}

	SDL_WM_SetCaption("Speed!",NULL);

	#ifdef _OS_WINDOWS_
	glActiveTextureARB=(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
	glMultiTexCoord2fARB=(PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
	if(!(glActiveTextureARB&&glMultiTexCoord2fARB))
	{
		fprintf(stderr,"Unable to initialize ARB extensions.\n");
		return 0;
	}
	#endif

	glClearColor(0,0,0,0);
	glViewport(0,0,screen->w,screen->h);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glShadeModel(GL_SMOOTH);
	glDisable(GL_CULL_FACE);

	glActiveTextureARB(GL_TEXTURE0);
	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

	glActiveTextureARB(GL_TEXTURE1);
//	glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_ADD);
	glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE);
	glTexEnvi(GL_TEXTURE_ENV,GL_COMBINE_RGB,GL_INTERPOLATE);

	glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE0_RGB,GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND0_RGB,GL_SRC_COLOR);

	glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE1_RGB,GL_PREVIOUS);
	glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND1_RGB,GL_SRC_COLOR);

	glTexEnvi(GL_TEXTURE_ENV,GL_SOURCE2_RGB,GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV,GL_OPERAND2_RGB,GL_SRC_ALPHA);

	FILE *fh=fopen(palfile,"rb");
	if(!fh)
	{
		fprintf(stderr,"Couldn't load palette %s",palfile);
		return 0;
	}

	for(int i=0;i<256;i++)
	{
		fseek(fh,1,SEEK_CUR);
		fread(&renderer->palette[i],3,1,fh);
	}
	fclose(fh);

	glGenTextures(NUM_MAP_TEXTURES,renderer->textures);
	glGenTextures(NUM_MAP_TEXTURES,renderer->lights);

	if(!InitFont(&renderer->font,"font2",16,32,16,16,0)) return 0;

	return 1;
}

/*void FreeRenderer()
{
	glDeleteTextures(NUM_MAP_TEXTURES,renderer->textures);
	glDeleteTextures(NUM_MAP_TEXTURES,renderer->lights);
}*/

int PrepareForMap(Renderer *renderer,Map *map)
{
	for(int i=0;i<NUM_MAP_TEXTURES;i++)
	{
		if(!map->texturenames[i][0]) continue;

		FILE *fh=fopen(map->texturenames[i],"rb");
		if(!fh)
		{
			fprintf(stderr,"Couldn't load texture %s",map->texturenames[i]);
			continue;
		}

		GLubyte texbuf[16384];
		fread(texbuf,16384,1,fh);
		fclose(fh);

		GLubyte normalbytes[16384*3];
		GLubyte lightbytes[16384*4];

		for(int y=0;y<128;y++)
		for(int x=0;x<128;x++)
		{
			GLubyte col=texbuf[x+y*128];
			GLubyte *normal=&normalbytes[3*(x+y*128)];
			GLubyte *light=&lightbytes[4*(x+y*128)];
			normal[0]=renderer->palette[col].r;
			normal[1]=renderer->palette[col].g;
			normal[2]=renderer->palette[col].b;

			if(col>128)
			{
				light[0]=renderer->palette[col].r;
				light[1]=renderer->palette[col].g;
				light[2]=renderer->palette[col].b;
				light[3]=0xff;
			}
			else
			{
				light[0]=0;
				light[1]=0;
				light[2]=0;
				light[3]=0;
			}
		}

		glBindTexture(GL_TEXTURE_2D,renderer->textures[i]);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGB8,128,128,GL_RGB,GL_UNSIGNED_BYTE,normalbytes);

		glBindTexture(GL_TEXTURE_2D,renderer->lights[i]);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA8,128,128,GL_RGBA,GL_UNSIGNED_BYTE,lightbytes);
	}
	return 1;
}

float CalcLight(int tile_x,int tile_y,int cam_x,int cam_y)
{
	float r=sqrt((tile_x-(float)cam_x/4096.0f)*(tile_x-(float)cam_x/4096.0f)+(tile_y-(float)cam_y/4096.0f)*(tile_y-(float)cam_y/4096.0f));
	if(r<1) return 1;
	else return 1/r;
}

float CalcTextureExtent(int high,int low)
{
	return ((float)high-(float)low)/1024.0f/1.6f;
}

void SetGLColor(PaletteEntry *p,int col,float l)
{
	if(col>=128)
	{
		glColor3ub(p[col].r,p[col].g,p[col].b);
	}
	else
	{
		glColor3f((float)p[col].r*l/255.0f,(float)p[col].g*l/255.0f,(float)p[col].b*l/255.0f);
	}
}

int AnimateTexture(int texture,int frame)
{
	if(texture==15) return texture+(frame&1);
	if(texture==16) return texture-(frame&1);
	return texture;
}

char *FormatTime(int time)
{
	static char str[6];
	sprintf(str,time<250?" %d:%02d":"%02d:%02d",time/25,(time%25)*4);
	return str;
}

void RenderWall(Renderer *renderer,int tex,float c1,float c2,float u1,float u2,int x1,int x2,int y1,int y2,int z1,int z2)
{
	glActiveTextureARB(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D,renderer->textures[tex]);
	glActiveTextureARB(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D,renderer->lights[tex]);

	glBegin(GL_QUADS);
	glColor3f(c1,c1,c1);
	glTexCoord2f(u1,0); glMultiTexCoord2fARB(GL_TEXTURE1,u1,0); glVertex3i(x1,z1,y1);
	glTexCoord2f(u2,0); glMultiTexCoord2fARB(GL_TEXTURE1,u2,0); glVertex3i(x1,z2,y1);
	glColor3f(c2,c2,c2);
	glTexCoord2f(u2,1); glMultiTexCoord2fARB(GL_TEXTURE1,u2,1); glVertex3i(x2,z2,y2);
	glTexCoord2f(u1,1); glMultiTexCoord2fARB(GL_TEXTURE1,u1,1); glVertex3i(x2,z1,y2);
	glEnd();
}

#define LIGHT 72
void Render(Renderer *renderer,Map *map,struct Game *game,int animticks)
{
	int zdist=24,xydist=zdist*4;
	int map_x=game->x>>12;
	int map_y=game->y>>12;
	int tile_x=game->x&0xfff;
	int tile_y=game->y&0xfff;
	int tile_z=game->z;

	if(tile_x<xydist && (tile_z<MapFloor(map,map_x-1,map_y)+zdist||tile_z>MapRoof(map,map_x-1,map_y)-zdist) ) tile_x=xydist;
	if(tile_y<xydist && (tile_z<MapFloor(map,map_x,map_y-1)+zdist||tile_z>MapRoof(map,map_x,map_y-1)-zdist) ) tile_y=xydist;
	if(tile_x>4095-xydist && (tile_z<MapFloor(map,map_x+1,map_y)+zdist||tile_z>MapRoof(map,map_x+1,map_y)-zdist) ) tile_x=4095-xydist;
	if(tile_y>4095-xydist && (tile_z<MapFloor(map,map_x,map_y+1)+zdist||tile_z>MapRoof(map,map_x,map_y+1)-zdist) ) tile_y=4095-xydist;

	if(tile_z<MapFloor(map,map_x,map_y)+zdist) tile_z=MapFloor(map,map_x,map_y)+zdist;
	if(tile_z>MapRoof(map,map_x,map_y)-zdist) tile_z=MapRoof(map,map_x,map_y)-zdist;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(75,(float)screen->w/(float)screen->h,0.01,32);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glRotatef((float)game->a/1024*360+90,0,1,0);
	glTranslatef(-(float)((map_x<<12)+tile_x)/4096.0,-(float)tile_z/1024.0,-(float)((map_y<<12)+tile_y)/4096.0),
	glScalef(1,1.0/1024.0,1);

	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	glDisable(GL_BLEND);
	glEnable(GL_DEPTH_TEST);

	for(int y=0;y<map->h;y++)
	for(int x=0;x<map->w;x++)
	{
		MapTile *tile=&map->tiles[x+y*map->w];

		glActiveTextureARB(GL_TEXTURE0);
		glDisable(GL_TEXTURE_2D);
		glActiveTextureARB(GL_TEXTURE1);
		glDisable(GL_TEXTURE_2D);

		glBegin(GL_QUADS);

		float c=(float)(LIGHT-tile->darkness)/LIGHT;
		float c00=CalcLight(x,y,game->x,game->y);
		float c10=CalcLight(x+1,y,game->x,game->y);
		float c01=CalcLight(x,y+1,game->x,game->y);
		float c11=CalcLight(x+1,y+1,game->x,game->y);

		if(game->z>=tile->floor && tile->floor<tile->roof)
		{
			SetGLColor(renderer->palette,tile->floorcol,c*c00); glVertex3i(x,tile->floor,y);
			SetGLColor(renderer->palette,tile->floorcol,c*c01); glVertex3i(x,tile->floor,y+1);
			SetGLColor(renderer->palette,tile->floorcol,c*c11); glVertex3i(x+1,tile->floor,y+1);
			SetGLColor(renderer->palette,tile->floorcol,c*c10); glVertex3i(x+1,tile->floor,y);
		}

		if(game->z<=tile->roof && tile->floor<tile->roof)
		{
			SetGLColor(renderer->palette,tile->roofcol,c*c00); glVertex3i(x,tile->roof,y);
			SetGLColor(renderer->palette,tile->roofcol,c*c10); glVertex3i(x+1,tile->roof,y);
			SetGLColor(renderer->palette,tile->roofcol,c*c11); glVertex3i(x+1,tile->roof,y+1);
			SetGLColor(renderer->palette,tile->roofcol,c*c01); glVertex3i(x,tile->roof,y+1);
		}

		glEnd();

		glActiveTextureARB(GL_TEXTURE0);
		glEnable(GL_TEXTURE_2D);
		glActiveTextureARB(GL_TEXTURE1);
		glEnable(GL_TEXTURE_2D);

		if(x>0)
		{
			MapTile *prevtile=&map->tiles[(x-1)+y*map->w];
			if(tile->floor<tile->roof || prevtile->floor<prevtile->roof)
			{
				if(prevtile->roof!=tile->roof)
				{
					int low,high,actual;
					float cw;
					if(prevtile->roof<tile->roof)
					{
						if(x>map_x) goto skip1;
						low=prevtile->roof; high=tile->roof; actual=prevtile->actualroof; cw=c;
					}
					else
					{
						if(x<=map_x) goto skip1;
						low=tile->roof; high=prevtile->roof; actual=tile->actualroof; cw=(float)(LIGHT-prevtile->darkness)/LIGHT;
					}
					float u=CalcTextureExtent(high,low);
					float u0=CalcTextureExtent(low,actual);
					int tex=AnimateTexture(tile->textures[0],animticks);
					RenderWall(renderer,tex,cw*c00,cw*c01,u0,u+u0,x,x,y,y+1,low,high);
				}

				skip1:

				if(prevtile->floor!=tile->floor)
				{
					int low,high;
					float cw;
					if(prevtile->floor<tile->floor)
					{
						if(x<=map_x) goto skip2;
						low=prevtile->floor; high=tile->floor; cw=(float)(LIGHT-prevtile->darkness)/LIGHT;
					}
					else
					{
						if(x>map_x) goto skip2;
						low=tile->floor; high=prevtile->floor; cw=c;
					}
					float u=CalcTextureExtent(high,low);
					int tex=AnimateTexture(tile->textures[2],animticks);
					RenderWall(renderer,tex,cw*c00,cw*c01,u,0,x,x,y,y+1,low,high);
				}
			}
		}

		skip2:

		if(y>0)
		{
			MapTile *prevtile=&map->tiles[x+(y-1)*map->w];

			if(tile->floor<tile->roof || prevtile->floor<prevtile->roof)
			{
				if(prevtile->roof!=tile->roof)
				{
					int low,high,actual;
					float cw;
					if(prevtile->roof<tile->roof)
					{
						if(y>map_y) goto skip3;
						low=prevtile->roof; high=tile->roof; actual=prevtile->actualroof; cw=c;
					}
					else
					{
						if(y<=map_y) goto skip3;
						low=tile->roof; high=prevtile->roof; actual=tile->actualroof; cw=(float)(LIGHT-prevtile->darkness)/LIGHT;
					}
					float u=CalcTextureExtent(high,low);
					float u0=CalcTextureExtent(low,actual);
					int tex=AnimateTexture(tile->textures[1],animticks);
					RenderWall(renderer,tex,cw*c00,cw*c10,u0,u+u0,x,x+1,y,y,low,high);
				}

				skip3:

				if(prevtile->floor!=tile->floor)
				{
					int low,high;
					float cw;
					if(prevtile->floor<tile->floor)
					{
						if(y<=map_y) goto skip4;
						low=prevtile->floor; high=tile->floor; cw=(float)(LIGHT-prevtile->darkness)/LIGHT;
					}
					else
					{
						if(y>map_y) goto skip4;
						low=tile->floor; high=prevtile->floor; cw=c;
					}
					float u=CalcTextureExtent(high,low);
					int tex=AnimateTexture(tile->textures[3],animticks);
					RenderWall(renderer,tex,cw*c00,cw*c10,u,0,x,x+1,y,y,low,high);
				}
			}
			skip4: 0;
		}
	}

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0,screen->w,screen->h,0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glActiveTextureARB(GL_TEXTURE1);
	glDisable(GL_TEXTURE_2D);
	glActiveTextureARB(GL_TEXTURE0);
	glEnable(GL_TEXTURE_2D);

	glDisable(GL_DEPTH_TEST);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

	float c=exp(-(game->frame-game->lasthit)/12.0);

	glBegin(GL_TRIANGLE_STRIP);
	glColor4f(0.75+c*0.25,c*0.25,c*0.25,0);
	glVertex2i(0,screen->h-56);
	glVertex2i(screen->w,screen->h-56);
	glColor4f(0.75+c*0.5,c*0.5,c*0.5,0.85+c*0.15);
	glVertex2i(0,screen->h-52);
	glVertex2i(screen->w,screen->h-52);
	glVertex2i(0,screen->h-14);
	glVertex2i(screen->w,screen->h-14);
	glColor4f(0.75+c*0.25,c*0.25,c*0.25,0);
	glVertex2i(0,screen->h-10);
	glVertex2i(screen->w,screen->h-10);
	glEnd();

	char *message,msgbuf[31];
	if(game->eventtype==GameLapEvent&&game->frame-game->eventtime<=200)
	{
		sprintf(msgbuf,"  \207\210 Full lap time  %s \207\210  ",FormatTime(game->lasttime));
		message=msgbuf;
	}
	else if(game->eventtype==GameGateEvent&&game->frame-game->eventtime<=100)
	{
		sprintf(msgbuf,"      \205\206 Go to gate %d \205\206      ",game->gate%4+1);
		message=msgbuf;
	}
	else
	{
		int num_messages=sizeof(messages)/sizeof(messages[0]);
		int n=(game->frame/100)%num_messages;
		message=messages[n];
	}

	glColor4f(0,0,0,0.25);
	DrawTextWithFont(&renderer->font,message,screen->w/2-332+2,screen->h-48+2);
	glColor4f(1,1,1,1);
	DrawTextWithFont(&renderer->font,message,screen->w/2-332,screen->h-48);

	char timestr[12];
	if(game->gate) sprintf(timestr,"\226\227%d \224\225%s",game->gate%4+1,FormatTime(game->timer));
	else strcpy(timestr,"\226\2271 \224\225 -:--");

	glColor4f(0,0,0,0.25);
	DrawTextWithFont(&renderer->font,timestr,screen->w/2+158+2,screen->h-48+2);
	glColor4f(1,1,1,1);
	DrawTextWithFont(&renderer->font,timestr,screen->w/2+158,screen->h-48);

	glFlush();
	SDL_GL_SwapBuffers();
}
